import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import json
import os
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import webbrowser
from pathlib import Path  # 添加Path模块用于处理路径

try:
    import openpyxl

    OPENPYXL_AVAILABLE = True
except ImportError:
    OPENPYXL_AVAILABLE = False


class SpotifySearchApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Spotify 音乐搜索工具")
        # 设置窗口初始状态为最大化
        self.root.state('zoomed')  # Windows系统
        # 对于Linux系统，可以使用: root.attributes('-zoomed', True)

        # 设置应用程序背景色
        self.root.configure(bg='#f8fafc')

        # 配置颜色方案
        self.colors = {
            'bg_primary': '#ffffff',
            'bg_secondary': '#f1f5f9',
            'bg_tertiary': '#e2e8f0',
            'text_primary': '#1e293b',
            'text_secondary': '#64748b',
            'accent_blue': '#3b82f6',
            'accent_light_blue': '#dbeafe',
            'border': '#e2e8f0'
        }

        # 配置样式
        self.style = ttk.Style()
        self.style.theme_use('clam')  # 使用clam主题作为基础
        self.configure_styles()

        # 创建配置文件夹 - 修改为使用AppData/Local目录
        self.config_dir = self.get_app_data_dir()
        if not os.path.exists(self.config_dir):
            os.makedirs(self.config_dir)

        # 加载配置
        self.config = self.load_config()

        # 初始化 Spotipy - 只在有凭据时初始化
        self.sp = None
        if self.config.get('client_id') and self.config.get('client_secret'):
            self.initialize_spotipy(show_error=False)  # 不显示初始化错误

        # 创建界面
        self.create_widgets()

        # 存储表格数据
        self.df = None

        # 如果没有openpyxl，禁用Excel导入功能
        if not OPENPYXL_AVAILABLE:
            self.disable_excel_import()

    def configure_styles(self):
        """配置应用程序的样式"""
        # 配置主框架样式
        self.style.configure('TFrame', background=self.colors['bg_primary'])

        # 配置标签样式
        self.style.configure('TLabel',
                             background=self.colors['bg_primary'],
                             foreground=self.colors['text_primary'])
        self.style.configure('Header.TLabel',
                             font=('Segoe UI', 12, 'bold'),
                             foreground=self.colors['accent_blue'])
        self.style.configure('Title.TLabel',
                             font=('Segoe UI', 10, 'bold'),
                             foreground=self.colors['text_primary'])
        self.style.configure('Secondary.TLabel',
                             foreground=self.colors['text_secondary'])

        # 配置按钮样式
        self.style.configure('TButton',
                             background=self.colors['accent_light_blue'],
                             foreground=self.colors['accent_blue'],
                             borderwidth=1,
                             focusthickness=0,
                             focuscolor=self.colors['accent_light_blue'])
        self.style.map('TButton',
                       background=[('active', self.colors['accent_blue'])],
                       foreground=[('active', '#ffffff')])

        # 配置输入框样式
        self.style.configure('TEntry',
                             fieldbackground=self.colors['bg_primary'],
                             foreground=self.colors['text_primary'],
                             bordercolor=self.colors['border'],
                             lightcolor=self.colors['border'],
                             darkcolor=self.colors['border'])

        # 配置复选框样式
        self.style.configure('TCheckbutton',
                             background=self.colors['bg_primary'],
                             foreground=self.colors['text_primary'])

        # 配置树状视图样式
        self.style.configure('Treeview',
                             background=self.colors['bg_primary'],
                             foreground=self.colors['text_primary'],
                             fieldbackground=self.colors['bg_primary'],
                             borderwidth=0,
                             rowheight=25)
        self.style.configure('Treeview.Heading',
                             background=self.colors['bg_secondary'],
                             foreground=self.colors['text_primary'],
                             relief='flat',
                             font=('Segoe UI', 9, 'bold'))
        self.style.map('Treeview.Heading',
                       background=[('active', self.colors['accent_light_blue'])])

        # 配置滚动条样式
        self.style.configure('TScrollbar',
                             background=self.colors['bg_secondary'],
                             troughcolor=self.colors['bg_primary'],
                             bordercolor=self.colors['border'],
                             arrowcolor=self.colors['text_secondary'])
        self.style.map('TScrollbar',
                       background=[('active', self.colors['accent_blue'])])

        # 配置标签框架样式
        self.style.configure('TLabelframe',
                             background=self.colors['bg_primary'],
                             foreground=self.colors['text_primary'],
                             bordercolor=self.colors['border'])
        self.style.configure('TLabelframe.Label',
                             background=self.colors['bg_primary'],
                             foreground=self.colors['accent_blue'])

    def get_app_data_dir(self):
        """获取AppData/Local目录下的应用数据文件夹路径"""
        # 获取AppData/Local目录
        app_data_local = os.getenv('LOCALAPPDATA')
        if not app_data_local:
            # 如果环境变量不存在，回退到用户主目录
            app_data_local = os.path.join(os.path.expanduser('~'), 'AppData', 'Local')

        # 创建应用特定的文件夹
        app_dir = os.path.join(app_data_local, 'Music_Search_Tool')
        return app_dir

    def disable_excel_import(self):
        pass

    def load_config(self):
        config_path = os.path.join(self.config_dir, "config.json")
        if os.path.exists(config_path):
            try:
                with open(config_path, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    # 设置默认值
                    if 'search_limit' not in config:
                        config['search_limit'] = 10
                    return config
            except:
                return {'search_limit': 10}
        return {'search_limit': 10}

    def save_config(self):
        config_path = os.path.join(self.config_dir, "config.json")
        with open(config_path, 'w', encoding='utf-8') as f:
            json.dump(self.config, f, ensure_ascii=False, indent=4)

    def initialize_spotipy(self, show_error=True):
        try:
            client_credentials_manager = SpotifyClientCredentials(
                client_id=self.config['client_id'],
                client_secret=self.config['client_secret']
            )
            self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
            return True
        except Exception as e:
            if show_error:
                messagebox.showerror("错误", f"Spotify 初始化失败: {str(e)}")
            return False

    def create_widgets(self):
        # 创建主容器，增加一些内边距
        main_container = ttk.Frame(self.root, padding="10")
        main_container.pack(fill=tk.BOTH, expand=True)

        # 创建顶部配置框架
        config_frame = ttk.LabelFrame(main_container, text="API 配置", padding="10")
        config_frame.pack(fill=tk.X, padx=5, pady=5)

        # Client ID
        ttk.Label(config_frame, text="Client ID:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.client_id_var = tk.StringVar(value=self.config.get('client_id', ''))
        client_id_entry = ttk.Entry(config_frame, textvariable=self.client_id_var, width=30)
        client_id_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        # 绑定焦点离开事件
        client_id_entry.bind("<FocusOut>", self.save_config_from_ui)

        # Client Secret
        ttk.Label(config_frame, text="Client Secret:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.client_secret_var = tk.StringVar(value=self.config.get('client_secret', ''))
        client_secret_entry = ttk.Entry(config_frame, textvariable=self.client_secret_var, width=30, show="*")
        client_secret_entry.grid(row=0, column=3, sticky=tk.W, padx=5, pady=5)
        # 绑定焦点离开事件
        client_secret_entry.bind("<FocusOut>", self.save_config_from_ui)

        # 搜索结果数量限制
        ttk.Label(config_frame, text="搜索结果数量:").grid(row=0, column=4, sticky=tk.W, padx=5, pady=5)
        self.search_limit_var = tk.StringVar(value=str(self.config.get('search_limit', 10)))
        search_limit_entry = ttk.Entry(config_frame, textvariable=self.search_limit_var, width=10)
        search_limit_entry.grid(row=0, column=5, sticky=tk.W, padx=5, pady=5)
        # 绑定焦点离开事件
        search_limit_entry.bind("<FocusOut>", self.save_config_from_ui)

        # 创建主分割窗口
        self.paned_window = tk.PanedWindow(main_container, orient=tk.HORIZONTAL, sashwidth=4,
                                           bg=self.colors['border'], sashrelief=tk.RAISED)
        self.paned_window.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 左侧框架 - 表格
        left_frame = ttk.Frame(self.paned_window, padding="10")
        self.paned_window.add(left_frame, width=1000)  # 增加左侧宽度

        # 右侧框架 - 搜索结果
        right_frame = ttk.Frame(self.paned_window, padding="10")
        self.paned_window.add(right_frame, width=400)  # 减小右侧宽度

        # 顶部控制框架
        control_frame = ttk.Frame(left_frame, padding="10")
        control_frame.pack(fill=tk.X, pady=(0, 10))

        # 导入文件按钮
        ttk.Button(control_frame, text="导入表格文件", command=self.import_file).pack(side=tk.LEFT, padx=5)

        # 使用艺术家搜索复选框
        self.use_artist_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(control_frame, text="使用艺术家搜索", variable=self.use_artist_var).pack(side=tk.LEFT, padx=5)

        # 搜索状态标签
        self.status_label = ttk.Label(control_frame, text="就绪", style='Secondary.TLabel')
        self.status_label.pack(side=tk.RIGHT, padx=5)

        # 表格框架
        table_frame = ttk.Frame(left_frame)
        table_frame.pack(fill=tk.BOTH, expand=True)

        # 创建表格 - 增加序号列
        columns = ("index", "title", "artist", "album", "genre", "year", "track_no")
        self.tree = ttk.Treeview(table_frame, columns=columns, show="headings", style='Treeview')

        # 设置列标题
        self.tree.heading("index", text="序号")
        self.tree.heading("title", text="歌曲名")
        self.tree.heading("artist", text="艺术家")
        self.tree.heading("album", text="专辑")
        self.tree.heading("genre", text="流派")
        self.tree.heading("year", text="年份")
        self.tree.heading("track_no", text="音轨号")

        # 设置列宽
        self.tree.column("index", width=50, anchor=tk.CENTER)
        self.tree.column("title", width=200)
        self.tree.column("artist", width=150)
        self.tree.column("album", width=200)
        self.tree.column("genre", width=150)
        self.tree.column("year", width=80, anchor=tk.CENTER)
        self.tree.column("track_no", width=80, anchor=tk.CENTER)

        # 添加滚动条
        scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)

        # 布局
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 绑定点击事件
        self.tree.bind("<ButtonRelease-1>", self.on_item_click)

        # 右侧搜索结果区域
        results_header = ttk.Label(right_frame, text="搜索结果", style='Header.TLabel')
        results_header.pack(anchor=tk.W, pady=(0, 10))

        # 创建搜索结果框架和滚动条
        results_container = ttk.Frame(right_frame)
        results_container.pack(fill=tk.BOTH, expand=True)

        # 创建画布和滚动条
        self.results_canvas = tk.Canvas(results_container, bg=self.colors['bg_primary'], highlightthickness=0)
        scrollbar = ttk.Scrollbar(results_container, orient=tk.VERTICAL, command=self.results_canvas.yview)
        self.scrollable_frame = ttk.Frame(self.results_canvas)

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.results_canvas.configure(scrollregion=self.results_canvas.bbox("all"))
        )

        self.results_canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.results_canvas.configure(yscrollcommand=scrollbar.set)

        self.results_canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        # 绑定鼠标滚轮事件到画布
        self.results_canvas.bind("<MouseWheel>", self._on_mousewheel)
        self.results_canvas.bind("<Button-4>", self._on_mousewheel)  # Linux 向上滚动
        self.results_canvas.bind("<Button-5>", self._on_mousewheel)  # Linux 向下滚动

        # 绑定鼠标滚轮事件到可滚动框架
        self.scrollable_frame.bind("<MouseWheel>", self._on_mousewheel)
        self.scrollable_frame.bind("<Button-4>", self._on_mousewheel)
        self.scrollable_frame.bind("<Button-5>", self._on_mousewheel)

    def save_config_from_ui(self, event=None):
        """从UI保存配置"""
        self.config['client_id'] = self.client_id_var.get()
        self.config['client_secret'] = self.client_secret_var.get()

        # 处理搜索结果数量限制
        try:
            search_limit = int(self.search_limit_var.get())
            if search_limit < 1 or search_limit > 50:
                messagebox.showerror("错误", "搜索结果数量必须在1到50之间")
                return
            self.config['search_limit'] = search_limit
        except ValueError:
            messagebox.showerror("错误", "搜索结果数量必须是有效的数字")
            return

        self.save_config()

        if self.initialize_spotipy(show_error=True):  # 保存配置时显示错误
            self.status_label.config(text="配置已保存并生效")
        else:
            self.status_label.config(text="配置保存失败，请检查凭据是否正确")

    def _on_mousewheel(self, event):
        """处理鼠标滚轮事件"""
        # Windows 和 MacOS
        if event.num == 4 or event.delta > 0:
            self.results_canvas.yview_scroll(-1, "units")
        elif event.num == 5 or event.delta < 0:
            self.results_canvas.yview_scroll(1, "units")

    def import_file(self):
        file_path = filedialog.askopenfilename(
            title="选择表格文件",
            filetypes=[("表格文件", "*.csv *.xls *.xlsx"), ("所有文件", "*.*")]
        )

        if not file_path:
            return

        try:
            if file_path.endswith('.csv'):
                self.df = pd.read_csv(file_path)
            elif file_path.endswith(('.xlsx', '.xls')):
                if not OPENPYXL_AVAILABLE:
                    messagebox.showerror(
                        "缺少依赖",
                        "需要 openpyxl 库来读取 Excel 文件。\n\n请使用以下命令安装：\npip install openpyxl\n\n或者使用 Conda：\nconda install openpyxl"
                    )
                    return
                self.df = pd.read_excel(file_path)
            else:
                messagebox.showerror("错误", "不支持的文件格式")
                return

            # 清空现有表格
            for item in self.tree.get_children():
                self.tree.delete(item)

            # 填充表格，添加序号
            for index, row in self.df.iterrows():
                self.tree.insert("", tk.END, values=(
                    index + 1,  # 序号
                    row.get('title', ''),
                    row.get('artist', ''),
                    row.get('album', ''),
                    row.get('genre', ''),
                    row.get('year', ''),
                    row.get('track_no', '')
                ))

            self.status_label.config(text=f"已导入 {len(self.df)} 条记录")

        except Exception as e:
            messagebox.showerror("错误", f"导入文件失败: {str(e)}")

    def on_item_click(self, event):
        if not self.sp:
            messagebox.showwarning("警告", "请先配置 Spotify API 凭据")
            return

        # 获取选中的项目
        selected_item = self.tree.focus()
        if not selected_item:
            return

        # 获取行数据
        item_data = self.tree.item(selected_item, "values")
        title = item_data[1]  # 索引1是标题，因为索引0现在是序号
        artist = item_data[2]  # 索引2是艺术家

        # 构建搜索查询
        if self.use_artist_var.get() and artist:
            query = f"{title} {artist}"
        else:
            query = title

        self.status_label.config(text=f"正在搜索: {query}")
        self.root.update()

        try:
            # 获取搜索结果数量限制
            search_limit = self.config.get('search_limit', 10)

            # 执行搜索
            results = self.sp.search(q=query, type='track', limit=search_limit)

            if results['tracks']['items']:
                # 显示搜索结果
                self.show_search_results(results, title, artist)
            else:
                self.clear_search_results()
                messagebox.showinfo("搜索结果", "未找到相关结果")

        except Exception as e:
            messagebox.showerror("错误", f"搜索失败: {str(e)}")

        self.status_label.config(text="就绪")

    def clear_search_results(self):
        # 清除现有搜索结果
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()

    def open_link(self, url):
        """打开浏览器链接"""
        webbrowser.open_new(url)

    def show_search_results(self, results, original_title, original_artist):
        # 清除现有搜索结果
        self.clear_search_results()

        # 搜索信息
        info_text = f"搜索: {original_title}"
        if original_artist:
            info_text += f" - {original_artist}"

        info_label = ttk.Label(self.scrollable_frame, text=info_text, style='Title.TLabel')
        info_label.pack(anchor=tk.W, pady=(0, 10))

        # 填充结果
        for track in results['tracks']['items']:
            # 创建结果框架 - 使用浅蓝色背景
            result_frame = tk.Frame(self.scrollable_frame,
                                    bg=self.colors['accent_light_blue'],
                                    relief=tk.FLAT,
                                    borderwidth=1,
                                    padx=5,
                                    pady=5)
            result_frame.pack(fill=tk.X, pady=5, padx=5)

            # 获取曲目信息
            title = track['name']
            artists = ", ".join([a['name'] for a in track['artists']])
            album = track['album']['name']
            year = track['album']['release_date'].split('-')[0] if track['album']['release_date'] else "未知"
            track_no = track.get('track_number', '未知')
            track_url = track['external_urls']['spotify']  # 获取Spotify链接

            # 尝试获取流派信息
            try:
                artist_id = track['artists'][0]['id']
                artist_info = self.sp.artist(artist_id)
                genre = ", ".join(artist_info['genres'][:3]) if artist_info['genres'] else "未知流派"
            except:
                genre = "未知流派"

            # 创建标签显示信息 - 使用自定义背景色
            title_label = tk.Label(result_frame, text=f"歌曲名: {title}",
                                   bg=self.colors['accent_light_blue'],
                                   fg=self.colors['text_primary'],
                                   font=('Segoe UI', 9, 'bold'),
                                   anchor='w')
            title_label.pack(fill=tk.X, pady=2)

            artist_label = tk.Label(result_frame, text=f"艺术家: {artists}",
                                    bg=self.colors['accent_light_blue'],
                                    fg=self.colors['text_primary'],
                                    anchor='w')
            artist_label.pack(fill=tk.X, pady=1)

            album_label = tk.Label(result_frame, text=f"专辑: {album}",
                                   bg=self.colors['accent_light_blue'],
                                   fg=self.colors['text_primary'],
                                   anchor='w')
            album_label.pack(fill=tk.X, pady=1)

            genre_label = tk.Label(result_frame, text=f"流派: {genre}",
                                   bg=self.colors['accent_light_blue'],
                                   fg=self.colors['text_primary'],
                                   anchor='w')
            genre_label.pack(fill=tk.X, pady=1)

            year_label = tk.Label(result_frame, text=f"年份: {year}",
                                  bg=self.colors['accent_light_blue'],
                                  fg=self.colors['text_primary'],
                                  anchor='w')
            year_label.pack(fill=tk.X, pady=1)

            track_label = tk.Label(result_frame, text=f"音轨号: {track_no}",
                                   bg=self.colors['accent_light_blue'],
                                   fg=self.colors['text_primary'],
                                   anchor='w')
            track_label.pack(fill=tk.X, pady=1)

            # 添加链接
            link_label = tk.Label(result_frame, text="在 Spotify 上播放",
                                  bg=self.colors['accent_light_blue'],
                                  fg=self.colors['accent_blue'],
                                  cursor="hand2",
                                  font=('Segoe UI', 9, 'underline'),
                                  anchor='w')
            link_label.pack(fill=tk.X, pady=(1, 0))
            link_label.bind("<Button-1>", lambda e, url=track_url: self.open_link(url))

        # 更新画布滚动区域
        self.results_canvas.configure(scrollregion=self.results_canvas.bbox("all"))
        # 滚动到顶部
        self.results_canvas.yview_moveto(0)


if __name__ == "__main__":
    root = tk.Tk()
    app = SpotifySearchApp(root)
    root.mainloop()